using System;
using System.Collections.Generic;
using System.Threading;

// ReSharper disable once CheckNamespace
namespace ZbUtil.IO.Ports
{
    internal class SerialPortDataHelper
    {
        #region 线程

        private Thread _threadWorker;
        private readonly ManualResetEvent _stopThreadEvent;

        #endregion

        #region 队列

        private readonly Queue<byte[]> _messageQueue;
        private readonly Semaphore _messageSemaphore;

        #endregion

        private readonly byte[] _buffer;
        private int _offset;
        private int _lastMessageTick;
        private readonly int _minBuffer; //缓最小存字节数，用来解决低延时问题，大于0时有效
        private readonly int _timeout;

        private readonly ISerialPortDataObserver _dataObserver;

        public SerialPortDataHelper(
            ISerialPortDataObserver dataObserver,
            int timeout = 128, int bufferSize = 4096)
            : this()
        {
            _dataObserver = dataObserver;
            _timeout = timeout;
            _buffer = new byte[bufferSize];
        }

        public SerialPortDataHelper(
            ISerialPortDataObserver dataObserver,
            int minBuffer)
            : this()
        {
            _dataObserver = dataObserver;
            var bufferSize = minBuffer < 4096 ? 4096 : minBuffer * 2;
            _buffer = new byte[bufferSize];
            _minBuffer = minBuffer;
        }

        private SerialPortDataHelper()
        {
            _stopThreadEvent = new ManualResetEvent(false);
            _messageQueue = new Queue<byte[]>();
            _messageSemaphore = new Semaphore(0, int.MaxValue);
        }

        public void Start()
        {
            //将事件状态设置为有信号，从而允许一个或多个等待线程继续执行
            _stopThreadEvent.Set();
            _threadWorker = new Thread(DoWork)
            {
                IsBackground = true
            };
            _threadWorker.Start();
        }

        public void Stop()
        {
            //将事件状态设置为非终止，从而导致线程受阻
            _stopThreadEvent.Reset();
            AddMessage(null);
            if (_threadWorker != null)
            {
                if (_threadWorker.IsAlive)
                {
                    _threadWorker.Join();
                }

                _threadWorker = null;
            }

            ClearMessage();
        }

        #region 队列操作

        public void AddMessage(byte[] message)
        {
            if (message == null)
            {
                return;
            }

            if (IsThreadWorking(1))
            {
                lock (_messageQueue)
                {
                    _messageQueue.Enqueue(message);
                }

                _messageSemaphore.Release();
            }
        }

        private byte[] PickMessage()
        {
            byte[] message = null;
            lock (_messageQueue)
            {
                if (_messageQueue.Count > 0)
                {
                    message = _messageQueue.Peek();
                    _messageQueue.Dequeue();
                }
            }

            return message;
        }

        private void ClearMessage()
        {
            lock (_messageQueue)
            {
                _messageQueue.Clear();
            }
        }

        #endregion

        /// <summary>
        /// 线程执行方法
        /// </summary>
        private void DoWork()
        {
            _lastMessageTick = Environment.TickCount;
            while (true)
            {
                if (!IsThreadWorking(1))
                {
                    break;
                }

                if (_messageSemaphore.WaitOne(1))
                {
                    var message = PickMessage();
                    HandleMessage(message);
                }

                DispatchData();
            }
        }

        private bool IsThreadWorking(int millisecondsTimeout)
        {
            return _stopThreadEvent.WaitOne(millisecondsTimeout);
        }

        #region HandleMessage

        private void HandleMessage(byte[] message)
        {
            if (_offset + message.Length > _buffer.Length)
            {
                ResetBuffer();
                return;
            }

            Buffer.BlockCopy(message, 0, _buffer, _offset, message.Length);
            _offset += message.Length;
        }

        private void DispatchData()
        {
            var minBufferReached = _minBuffer > 0 && _offset >= _minBuffer;
            var timeoutReached = _minBuffer == 0 && Environment.TickCount - _lastMessageTick >= _timeout;
            if (minBufferReached || timeoutReached)
            {
                var buffer = new byte[_offset];
                Buffer.BlockCopy(_buffer, 0, buffer, 0, _offset);
                _dataObserver?.ReceiveData(buffer);
                ResetBuffer();
            }

            if (timeoutReached)
            {
                _lastMessageTick = Environment.TickCount;
            }
        }

        private void ResetBuffer()
        {
            _offset = 0;
        }

        #endregion
    }
}
